package net.protocol.diameter.avp;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;

/**
 * <pre>
 *    Address
 *       The Address format is derived from the OctetString AVP Base
 *       Format.  It is a discriminated union, representing, for example a
 *       32-bit (IPv4) [IPV4] or 128-bit (IPv6) [IPV6] address, most
 *       significant octet first.  The first two octets of the Address
 *       AVP represents the AddressType, which contains an Address Family
 *       defined in [IANAADFAM].  The AddressType is used to discriminate
 *       the content and format of the remaining octets.
 * 
 * </pre>
 *  
 * @author jinhongw@gmail.com
 * @see <a href="http://www.iana.org/assignments/address-family-numbers/address-family-numbers.xml">Address Family Numbers</a>
 */
public class Address extends AbstractAVP<InetAddress> {
    /**
     * Specify the address family: Internet Protocol, Version 4
     */
    static final int IPv4 = 1;

    /**
     * Specify the address family: Internet Protocol, Version 6
     */
    static final int IPv6 = 2;

	private final static int AType_DATA_LENGTH = 2;
	
	private final static int IPV4_DATA_LENGTH = 4;
	private final static int IPV6_DATA_LENGTH = 16;

	private short type;
	private InetAddress address;
	
    /**
     * @param code     AVP Code
     * @param address  InetAddress
     */
	public Address(int code, InetAddress address) {
		 this(code, DEFAULT_VENDOR_ID, address);
	}

    /**
     * @param code     AVP Code
     * @param vendorId Vendor-ID
     * @param address  InetAddress
     */
	public Address(int code, int vendorId, InetAddress address) {
		super(code, vendorId, getDataSize(address));

		this.type = type(address);
		this.address = address;
	}

    /**
     * Creates a new <code>Address</code> with the given DiameterAVPHeader and source ByteBuffer
     *
     * @param header Diameter AVP Header
     * @param src The buffer from which bytes are to be retrieved
     */
    Address(DiameterAVPHeader header, ByteBuffer src) {
        super(header, src);

        type = src.getShort();

        int length = (type == IPv4) ? IPV4_DATA_LENGTH : IPV6_DATA_LENGTH;
        byte[] addr = new byte[length];
        src.get(addr);
        try {
            address = InetAddress.getByAddress(addr);
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }

	@Override
	public ByteBuffer byteMe(ByteBuffer dst) {
		super.byteMe(dst);

		dst.putShort(type);
		dst.put(address.getAddress());
		
		return dst;
	}

	@Override
	public InetAddress getData() {
		return address;
	}
	
	@Override
	public byte[] getDataBytes() {
		int capacity = (type == IPv4) ? (AType_DATA_LENGTH + IPV4_DATA_LENGTH)
				: (AType_DATA_LENGTH + IPV6_DATA_LENGTH);
		ByteBuffer buf = ByteBuffer.allocate(capacity);
		buf.putShort(type);
		buf.put(address.getAddress());
		buf.flip();
		return buf.array();
	}

	@Override
	public final boolean isGroup() {
		return false;
	}

	@Override
	public final DataFormat getType() {
		return DataFormat.Address;
	}

	@Override
	public String getName() {
		return DataFormat.Address.name();
	}

	@Override
	protected void detail(StringBuilder buf) {
		buf.append(address.getHostAddress());
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (!super.equals(obj))
			return false;
		if (getClass() != obj.getClass())
			return false;
		Address other = (Address) obj;
		if (type != other.type)
			return false;
		if (address == null) {
			if (other.address != null)
				return false;
		} else if (!address.equals(other.address))
			return false;
		return true;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode() + type;
        result = prime * result + type;
		result = prime * result + ((address == null) ? 0 : address.hashCode());
		return result;
	}

	private static int getDataSize(InetAddress addr) {
		if (addr instanceof Inet4Address)
			return AType_DATA_LENGTH + IPV4_DATA_LENGTH;
		return AType_DATA_LENGTH + IPV6_DATA_LENGTH;
	}

	private static short type(InetAddress addr) {
		if (addr instanceof Inet4Address)
			return IPv4;
		return IPv6;
	}
}
